/*
 *
 * Copyright(c) 2010 Safelayer Secure Communications
 *
 * This is open source software, placed under the terms of the
 * GNU GENERAL PUBLIC LICENSE Version 3.
 * Please see: http://www.gnu.org/licenses/gpl-3.0.txt
 * 
 * Permission to use, copy, modify, and distribute this software
 * for any purpose and without fee is hereby granted, provided
 * that the above copyright notices appear in all copies and that
 * both the copyright notice and this permission notice appear in
 * supporting documentation.
 * 
 */
package com.neuralnoise.foaf.core;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Selector;
import com.hp.hpl.jena.rdf.model.SimpleSelector;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;

/**
 * This class provides FOAF utilities over an RDF graph.
 * 
 * @author victor.martinez <swtp@safelayer.com>
 * 
 */
public class FOAF {

	private Model model;

	public FOAF(Model model) {
		this.model = model;
	}

	/**
	 * Find all FOAF person representations included in this {@link FOAF} instance.
	 * 
	 * @return a List of {@link Person} instances.
	 */
	public List<Person> searchAllPeople() {
		return searchPeopleUsingSelector(new SimpleSelector(null, RDF.type, FOAFVocabulary.Person), true);
	}

	/**
	 * Search the primary person in this {@link FOAF} instance.
	 * 
	 * @param uri can be the uri of the document, the uri of the primary person or an empty String.
	 * @return the primary person in this {@link FOAF} object if it is found else return <tt>null</tt>
	 */
	public Person searchPrimaryPerson(String uri) {
		Resource primaryPerson = SearchUtils.searchPrimaryPerson(model, uri);
		if (primaryPerson == null)
			return null;

		Person beanPerson = FOAFConverter.toPerson(primaryPerson);
		return beanPerson;
	}

	/**
	 * Return all FOAF contacts included in this {@link FOAF} instance. First Attempt is to find the
	 * primary person in this {@link FOAF} instance, then retrieves contacts from the primary Person.
	 * 
	 * @return the FOAF contacts from the primary Person.
	 */
	public List<Person> searchContactsFromPrimaryPerson() {
		List<Person> people = new ArrayList<Person>();
		Resource primaryPerson = SearchUtils.searchPrimaryPerson(model, "");
		if (primaryPerson == null)
			return people;
		// retrieve contacts
		NodeIterator contactsIter = model.listObjectsOfProperty(primaryPerson, FOAFVocabulary.knows);
		while (contactsIter.hasNext()) {
			RDFNode node = contactsIter.next();
			if (node.isResource()) {
				Resource resource = (Resource) node; // safe cast
				Person personBean = FOAFConverter.toPerson(resource);
				people.add(personBean);
			}
		}
		return people;
	}

	/**
	 * Returns a list of {@link Person} selected by the given selector object.
	 * 
	 * @param selector A selector object used to filter the list of model statements according
	 *            to the selector criteria (s/p/o).
	 * @return the list of {@link Person} selected by the given selector object.
	 */
	public List<Person> searchPeopleUsingSelector(Selector selector, boolean includeDuplicates) {
		List<Person> people = new ArrayList<Person>();
		StmtIterator iterator = model.listStatements(selector);
		while (iterator.hasNext()) {
			Statement statement = (Statement) iterator.next();
			Resource subject = (Resource) statement.getSubject();
			Person beanPerson = FOAFConverter.toPerson(subject);
			people.add(beanPerson);
		}
		if (!includeDuplicates) {
			// remove duplicates preserving insertion
			Set<Person> s = new LinkedHashSet<Person>(people);
			people = new ArrayList<Person>(s);
		}
		return people;
	}
	
	/**
	 * Dynamic union of all input FOAF memory models. The contacts are merge based on knowledge of
	 * uniquely identifying properties base on predefine <code>trust</code> smushing rules include
	 * on this API.
	 * 
	 * @param models The FOAF memory models to merge.
	 * @return a model which represent the union of all input FOAF memory models.
	 */
	public static Model trustSmushing(List<Model> models, String uri) {
		return smushing(models, uri, true);
	}

	/**
	 * Dynamic union of all input FOAF memory models. The contacts are merge based on knowledge of
	 * non uniquely identifying properties (only an approach) base on predefine <code>untrust</code>
	 * smushing rules include on this API. This method should be use only as alternative of the
	 * method {@link FOAF#trustSmushing(List)} if the method don't provide you a satisfactory results.
	 * 
	 * @param models The FOAF memory models to merge.
	 * @return a model which represent the union of all input FOAF memory models.
	 */
	public static Model untrustSmushing(List<Model> models, String uri) {
		return smushing(models, uri, false);
	}
	
	// Helpers -----------------------------------------------------------------------------------
	
	private static Model smushing(List<Model> models, String uri, boolean trustSmushing) {
		List<Resource> primaryPeopleToMerge = MergeEngine.getPrimaryPeople(models);
		Model primaryPerson = MergeEngine.mergeResources(primaryPeopleToMerge);
		List<WrappedContact> contactsToMerge = MergeEngine.getAllContacts(models);
		List<Resource> contactsMerged = MergeEngine.mergeContacts(contactsToMerge, trustSmushing);
		primaryPerson = MergeEngine.addContacts(primaryPerson,contactsMerged);
		primaryPerson = MergeEngine.renamePrimaryPerson(primaryPerson, MergeEngine.TEMPORAL_PERSON_URI, uri);
		return primaryPerson;
	}

}